<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>多X轴单Y轴折线图</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.14/lib/index.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/element-ui@2.15.14/lib/theme-chalk/index.css"
    />
    <style>
      .chart-container {
        width: 90%;
        height: 500px;
        margin: 20px auto;
      }
      .filter-container {
        width: 90%;
        margin: 20px auto;
        display: flex;
        gap: 20px;
        align-items: center;
        flex-wrap: wrap;
      }
      .filter-item {
        display: flex;
        flex-direction: column;
        gap: 5px;
      }
      .filter-item label {
        font-size: 14px;
        color: #606266;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="filter-container">
        <div class="filter-item">
          <label>筛选人员:</label>
          <el-select
            v-model="filters.persons"
            multiple
            placeholder="请选择人员"
            style="width: 200px"
          >
            <el-option label="全部" value=""></el-option>
            <el-option
              v-for="person in uniquePersons"
              :key="person"
              :label="person"
              :value="person"
            ></el-option>
          </el-select>
        </div>

        <div class="filter-item">
          <label>筛选班次:</label>
          <el-select
            v-model="filters.classes"
            placeholder="请选择班次"
            style="width: 150px"
          >
            <el-option label="全部" value=""></el-option>
            <el-option
              v-for="cls in uniqueClasses"
              :key="cls"
              :label="cls"
              :value="cls"
            ></el-option>
          </el-select>
        </div>

        <div class="filter-item">
          <label>筛选类型:</label>
          <el-select
            v-model="filters.type"
            placeholder="请选择类型"
            style="width: 150px"
          >
            <el-option label="全部" value=""></el-option>
            <el-option
              v-for="type in uniqueTypes"
              :key="type"
              :label="type"
              :value="type"
            ></el-option>
          </el-select>
        </div>

        <div class="filter-item">
          <label>筛选公司:</label>
          <el-select
            v-model="filters.company"
            placeholder="请选择公司"
            style="width: 150px"
          >
            <el-option label="全部" value=""></el-option>
            <el-option
              v-for="company in uniqueCompanies"
              :key="company"
              :label="company"
              :value="company"
            ></el-option>
          </el-select>
        </div>

        <div class="filter-item">
          <label>数据展示方式:</label>
          <el-select
            v-model="filters.dataMode"
            placeholder="请选择展示方式"
            style="width: 150px"
          >
            <el-option label="平均值" value="average"></el-option>
            <el-option label="所有值" value="all"></el-option>
          </el-select>
        </div>

        <el-button type="primary" @click="updateChart">更新图表</el-button>
      </div>

      <div class="chart-container">
        <div id="multiXAxisChart" style="width: 100%; height: 100%"></div>
      </div>

      <div class="table-container" style="width: 90%; margin: 20px auto">
        <el-table
          :data="tableData"
          border
          style="width: 100%"
          :span-method="objectSpanMethod"
        >
          <!--      <el-table-column prop="classes" label="班次" width="100"></el-table-column>-->
          <el-table-column
            prop="type"
            label="类型"
            width="100"
          ></el-table-column>
          <el-table-column
            prop="company"
            label="单位"
            width="120"
          ></el-table-column>
          <el-table-column
            prop="person"
            label="人员"
            width="100"
          ></el-table-column>
          <el-table-column prop="rate" label="值" width="150">
            <template slot-scope="scope"> {{ scope.row.rate }} </template>
          </el-table-column>
          <el-table-column prop="average" label="平均值" width="100">
            <template slot-scope="scope">
              {{ (scope.row.average * 100).toFixed(2) }}%
            </template>
          </el-table-column>
          <el-table-column prop="max" label="最大值" width="100">
            <template slot-scope="scope">
              {{ (scope.row.max * 100).toFixed(2) }}%
            </template>
          </el-table-column>
          <el-table-column prop="min" label="最小值" width="100">
            <template slot-scope="scope">
              {{ (scope.row.min * 100).toFixed(2) }}%
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>

    <script>
      new Vue({
        el: "#app",
        data() {
          return {
            chart: null,
            filters: {
              persons: [],
              classes: "",
              type: "",
              company: "",
              dataMode: "average", // 默认显示平均值
            },
            chartData: [],
          };
        },
        computed: {
          uniquePersons() {
            return [...new Set(this.chartData.map((d) => d.person))];
          },
          uniqueClasses() {
            return [...new Set(this.chartData.map((d) => d.classes))];
          },
          uniqueTypes() {
            return [...new Set(this.chartData.map((d) => d.type))];
          },
          uniqueCompanies() {
            return [...new Set(this.chartData.map((d) => d.company))];
          },
          filteredData() {
            let data = this.chartData;

            if (
              this.filters.persons.length > 0 &&
              !this.filters.persons.includes("")
            ) {
              data = data.filter((d) =>
                this.filters.persons.includes(d.person),
              );
            }
            if (this.filters.classes) {
              data = data.filter((d) => d.classes === this.filters.classes);
            }
            if (this.filters.type) {
              data = data.filter((d) => d.type === this.filters.type);
            }
            if (this.filters.company) {
              data = data.filter((d) => d.company === this.filters.company);
            }

            return data;
          },
          tableData() {
            // 按类型、公司、人员分组计算统计数据
            const groupedData = {};

            this.filteredData.forEach(d => {
              const key = `${d.type}|${d.company}|${d.person}`;
              if (!groupedData[key]) {
                groupedData[key] = {
                  type: d.type,
                  company: d.company,
                  person: d.person,
                  rates: []
                };
              }
              groupedData[key].rates.push(d.rate);
            });

            // 转换为表格数据并计算统计值
            return Object.values(groupedData)
              .map(group => {
                const rates = group.rates;
                const average = rates.reduce((sum, rate) => sum + rate, 0) / rates.length;
                const max = Math.max(...rates);
                const min = Math.min(...rates);

                return {
                  type: group.type,
                  company: group.company,
                  person: group.person,
                  rate: rates.map(rate => (rate * 100).toFixed(2)).join(', '), // 显示所有值
                  average: average,
                  max: max,
                  min: min
                };
              })
              .sort((a, b) => {
                // 按类型、公司、人员排序
                if (a.type !== b.type) return a.type.localeCompare(b.type);
                if (a.company !== b.company) return a.company.localeCompare(b.company);
                return a.person.localeCompare(b.person);
              });
          },
        },
        methods: {
          getRandomInt(min, max) {
            min = Math.ceil(min);
            max = Math.floor(max);
            return Math.floor(Math.random() * (max - min + 1)) + min;
          },
          initData() {
            const companies = ["A", "B", "C", "D", "E", "F"];
            const person = ["张三", "李四", "王五"];
            const classes = ["白班", "夜班", "中班"];

            const data = [...Array(40).keys()].map((_, i) => {
              return {
                company: `${companies[this.getRandomInt(0, 5)]}公司`,
                person: person[this.getRandomInt(0, 2)],
                type: `类型${this.getRandomInt(1, 3)}`,
                classes: classes[this.getRandomInt(0, 2)],
                rate: this.getRandomInt(1, 100) / 100,
              };
            });
            const data1 = [
              {
                company: "B公司",
                person: "李四",
                type: "类型1",
                classes: "中班",
                rate: 0.11,
              },
              {
                company: "D公司",
                person: "张三",
                type: "类型3",
                classes: "白班",
                rate: 0.68,
              },
              {
                company: "A公司",
                person: "李四",
                type: "类型3",
                classes: "夜班",
                rate: 0.22,
              },
              {
                company: "E公司",
                person: "李四",
                type: "类型2",
                classes: "夜班",
                rate: 0.18,
              },
              {
                company: "E公司",
                person: "王五",
                type: "类型3",
                classes: "夜班",
                rate: 0.6,
              },
              {
                company: "E公司",
                person: "张三",
                type: "类型2",
                classes: "白班",
                rate: 0.72,
              },
              {
                company: "D公司",
                person: "张三",
                type: "类型3",
                classes: "白班",
                rate: 0.15,
              },
              {
                company: "D公司",
                person: "张三",
                type: "类型3",
                classes: "中班",
                rate: 0.12,
              },
              {
                company: "C公司",
                person: "张三",
                type: "类型2",
                classes: "夜班",
                rate: 0.8,
              },
              {
                company: "F公司",
                person: "李四",
                type: "类型3",
                classes: "白班",
                rate: 0.76,
              },
              {
                company: "B公司",
                person: "李四",
                type: "类型2",
                classes: "白班",
                rate: 0.99,
              },
              {
                company: "B公司",
                person: "张三",
                type: "类型2",
                classes: "中班",
                rate: 0.45,
              },
              {
                company: "B公司",
                person: "王五",
                type: "类型1",
                classes: "夜班",
                rate: 0.42,
              },
              {
                company: "D公司",
                person: "李四",
                type: "类型1",
                classes: "夜班",
                rate: 0.04,
              },
              {
                company: "B公司",
                person: "王五",
                type: "类型2",
                classes: "中班",
                rate: 0.66,
              },
              {
                company: "E公司",
                person: "李四",
                type: "类型3",
                classes: "中班",
                rate: 0.72,
              },
              {
                company: "A公司",
                person: "李四",
                type: "类型3",
                classes: "中班",
                rate: 0.17,
              },
              {
                company: "E公司",
                person: "李四",
                type: "类型3",
                classes: "夜班",
                rate: 0.15,
              },
              {
                company: "B公司",
                person: "李四",
                type: "类型2",
                classes: "中班",
                rate: 0.18,
              },
              {
                company: "D公司",
                person: "张三",
                type: "类型1",
                classes: "夜班",
                rate: 0.68,
              },
              {
                company: "A公司",
                person: "李四",
                type: "类型3",
                classes: "中班",
                rate: 0.29,
              },
              {
                company: "F公司",
                person: "李四",
                type: "类型3",
                classes: "白班",
                rate: 0.2,
              },
              {
                company: "E公司",
                person: "王五",
                type: "类型1",
                classes: "中班",
                rate: 0.06,
              },
              {
                company: "A公司",
                person: "李四",
                type: "类型1",
                classes: "中班",
                rate: 0.69,
              },
              {
                company: "B公司",
                person: "李四",
                type: "类型1",
                classes: "白班",
                rate: 0.03,
              },
              {
                company: "B公司",
                person: "张三",
                type: "类型2",
                classes: "中班",
                rate: 0.63,
              },
              {
                company: "D公司",
                person: "张三",
                type: "类型3",
                classes: "中班",
                rate: 0.19,
              },
              {
                company: "C公司",
                person: "王五",
                type: "类型1",
                classes: "夜班",
                rate: 0.13,
              },
              {
                company: "E公司",
                person: "李四",
                type: "类型3",
                classes: "中班",
                rate: 0.21,
              },
              {
                company: "D公司",
                person: "张三",
                type: "类型1",
                classes: "白班",
                rate: 0.31,
              },
              {
                company: "A公司",
                person: "张三",
                type: "类型2",
                classes: "中班",
                rate: 0.98,
              },
              {
                company: "C公司",
                person: "张三",
                type: "类型3",
                classes: "中班",
                rate: 0.01,
              },
              {
                company: "A公司",
                person: "王五",
                type: "类型3",
                classes: "白班",
                rate: 0.5,
              },
              {
                company: "F公司",
                person: "张三",
                type: "类型3",
                classes: "夜班",
                rate: 0.63,
              },
              {
                company: "E公司",
                person: "张三",
                type: "类型1",
                classes: "夜班",
                rate: 0.44,
              },
              {
                company: "F公司",
                person: "王五",
                type: "类型1",
                classes: "夜班",
                rate: 0.34,
              },
              {
                company: "D公司",
                person: "王五",
                type: "类型3",
                classes: "中班",
                rate: 0.9,
              },
              {
                company: "E公司",
                person: "李四",
                type: "类型1",
                classes: "夜班",
                rate: 0.86,
              },
              {
                company: "C公司",
                person: "张三",
                type: "类型1",
                classes: "白班",
                rate: 0.7,
              },
              {
                company: "A公司",
                person: "王五",
                type: "类型1",
                classes: "中班",
                rate: 0.67,
              },
            ];
            console.log(`aaabbb this.chartData`, data1);
            this.chartData = data1;
          },
          generateChartOption(data) {
            // 按班次、类型、公司的组合进行分组
            const groupedData = {};
            data.forEach((d) => {
              const key = `${d.classes}|${d.type}|${d.company}`;
              if (!groupedData[key]) {
                groupedData[key] = {
                  classes: d.classes,
                  type: d.type,
                  company: d.company,
                  persons: [],
                  rates: [],
                };
              }
              groupedData[key].persons.push(d.person);
              groupedData[key].rates.push(d.rate);
            });

            // 按班次排序并生成标签
            const sortedGroups = Object.values(groupedData).sort((a, b) => {
              if (a.classes !== b.classes)
                return a.classes.localeCompare(b.classes);
              if (a.type !== b.type) return a.type.localeCompare(b.type);
              return a.company.localeCompare(b.company);
            });

            // 生成X轴数据和系列数据
            const xAxisData = [];
            const seriesData = {};

            sortedGroups.forEach((group, index) => {
              xAxisData.push(`${group.classes}-${group.type}-${group.company}`);

              // 按人员分组
              const uniquePersons = [...new Set(group.persons)];
              uniquePersons.forEach((person) => {
                const personRates = group.rates.filter(
                  (rate, i) => group.persons[i] === person,
                );

                if (personRates.length > 0) {
                  if (this.filters.dataMode === "average") {
                    // 计算平均值
                    if (!seriesData[person]) {
                      seriesData[person] = new Array(sortedGroups.length).fill(
                        null,
                      );
                    }
                    const personAvg =
                      personRates.reduce((sum, rate) => sum + rate, 0) /
                      personRates.length;
                    seriesData[person][index] = personAvg;
                  } else {
                    // 显示所有值，为每个值创建单独的系列
                    personRates.forEach((rate, rateIndex) => {
                      const seriesKey = `${person}_${rateIndex + 1}`;
                      if (!seriesData[seriesKey]) {
                        seriesData[seriesKey] = new Array(
                          sortedGroups.length,
                        ).fill(null);
                      }
                      seriesData[seriesKey][index] = rate;
                    });
                  }
                }
              });
            });

            // 创建系列配置
            const series = Object.keys(seriesData).map((seriesKey, index) => {
              let displayName = seriesKey;
              let lineStyle = { width: 2 };

              // 如果是显示所有值模式，调整显示名称和样式
              if (this.filters.dataMode === "all" && seriesKey.includes("_")) {
                const [person, valueIndex] = seriesKey.split("_");
                displayName = `${person}-${valueIndex}`;
                // 同一人员的不同值使用相似颜色但不同样式
                lineStyle = {
                  width: 2,
                  type: parseInt(valueIndex) % 2 === 0 ? "solid" : "dashed",
                };
              }

              return {
                name: displayName,
                type: "line",
                data: seriesData[seriesKey],
                smooth: true,
                symbol: "circle",
                symbolSize: 6,
                lineStyle: lineStyle,
                connectNulls: true,
              };
            });

            // 生成分组信息
            const groupInfo = sortedGroups.map((group) => ({
              persons: [...new Set(group.persons)],
              classes: group.classes,
              type: group.type,
              company: group.company,
            }));

            // 生成合并后的轴标签和分割线
            function generateMergedLabelsWithSplit(data, key) {
              const labels = [];
              const splitIndexes = [];
              let lastValue = null;
              let spanStart = 0;

              data.forEach((item, index) => {
                const currentValue = item[key];
                if (currentValue !== lastValue) {
                  if (lastValue !== null) {
                    const centerIndex = Math.floor((spanStart + index - 1) / 2);
                    labels[centerIndex] = lastValue;
                    if (index > 0) splitIndexes.push(index - 0.5);
                  }
                  spanStart = index;
                  lastValue = currentValue;
                }
                if (!labels[index]) labels[index] = "";
              });

              if (lastValue !== null) {
                const centerIndex = Math.floor(
                  (spanStart + data.length - 1) / 2,
                );
                labels[centerIndex] = lastValue;
              }

              return { labels, splitIndexes };
            }

            return {
              title: {
                text: "多X轴单Y轴折线图",
                left: "center",
              },
              tooltip: {
                trigger: "axis",
                formatter: function (params) {
                  const dataIndex = params[0].dataIndex;
                  const group = groupInfo[dataIndex];
                  let result = `${group.company} - ${group.type} - ${group.classes}<br/>`;
                  params.forEach((param) => {
                    if (param.value !== null) {
                      result += `${param.seriesName}: ${((param.value || 0) * 100).toFixed(2)}%<br/>`;
                    }
                  });
                  return result;
                },
              },
              legend: {
                data: Object.keys(seriesData),
                top: 30,
              },
              grid: {
                left: "3%",
                right: "4%",
                bottom: "25%",
                top: "15%",
                containLabel: true,
              },
              xAxis: [
                {
                  type: "category",
                  data: generateMergedLabelsWithSplit(groupInfo, "classes")
                    .labels,
                  position: "bottom",
                  offset: 80,
                  axisLabel: {
                    interval: 0,
                    rotate: 0,
                    fontSize: 10,
                    margin: 8,
                  },
                  axisTick: {
                    show: false,
                  },
                  axisLine: {
                    show: true,
                  },
                  name: "班次",
                  nameLocation: "middle",
                  nameGap: 25,
                },
                {
                  type: "category",
                  data: generateMergedLabelsWithSplit(groupInfo, "type").labels,
                  position: "bottom",
                  offset: 40,
                  axisLabel: {
                    interval: 0,
                    rotate: 0,
                    fontSize: 10,
                    margin: 8,
                  },
                  axisTick: {
                    show: false,
                  },
                  axisLine: {
                    show: true,
                  },
                  name: "类型",
                  nameLocation: "middle",
                  nameGap: 25,
                },
                {
                  type: "category",
                  data: generateMergedLabelsWithSplit(groupInfo, "company")
                    .labels,
                  position: "bottom",
                  offset: 0,
                  axisLabel: {
                    interval: 0,
                    rotate: 0,
                    fontSize: 10,
                    margin: 8,
                  },
                  axisTick: {
                    show: false,
                  },
                  axisLine: {
                    show: true,
                  },
                  name: "公司",
                  nameLocation: "middle",
                  nameGap: 25,
                },
              ],
              yAxis: {
                type: "value",
                name: "平均通过率",
                axisLabel: {
                  formatter: function (value) {
                    return (value * 100).toFixed(1) + "%";
                  },
                },
              },
              series: series,
              graphic: [
                // 添加班次分组的背景区域
                ...(() => {
                  const classesInfo = generateMergedLabelsWithSplit(
                    groupInfo,
                    "classes",
                  );
                  const graphics = [];
                  let start = 0;

                  classesInfo.splitIndexes.forEach((splitIndex, i) => {
                    const end = splitIndex;
                    const startX = (start / groupInfo.length) * 100;
                    const endX = (end / groupInfo.length) * 100;

                    graphics.push({
                      type: "rect",
                      shape: {
                        x: startX + "%",
                        y: 380,
                        width: endX - startX + "%",
                        height: 30,
                      },
                      style: {
                        fill: i % 2 === 0 ? "#e8f5e8" : "#fff3e0",
                        stroke: "#999",
                        lineWidth: 1,
                      },
                    });

                    start = end;
                  });

                  if (start < groupInfo.length) {
                    const startX = (start / groupInfo.length) * 100;
                    graphics.push({
                      type: "rect",
                      shape: {
                        x: startX + "%",
                        y: 380,
                        width: 100 - startX + "%",
                        height: 30,
                      },
                      style: {
                        fill:
                          classesInfo.splitIndexes.length % 2 === 0
                            ? "#e8f5e8"
                            : "#fff3e0",
                        stroke: "#999",
                        lineWidth: 1,
                      },
                    });
                  }

                  return graphics;
                })(),
                // 添加类型分组的背景区域
                ...(() => {
                  const typeInfo = generateMergedLabelsWithSplit(
                    groupInfo,
                    "type",
                  );
                  const graphics = [];
                  let start = 0;

                  typeInfo.splitIndexes.forEach((splitIndex, i) => {
                    const end = splitIndex;
                    const startX = (start / groupInfo.length) * 100;
                    const endX = (end / groupInfo.length) * 100;

                    graphics.push({
                      type: "rect",
                      shape: {
                        x: startX + "%",
                        y: 420,
                        width: endX - startX + "%",
                        height: 30,
                      },
                      style: {
                        fill: i % 2 === 0 ? "#e3f2fd" : "#f3e5f5",
                        stroke: "#666",
                        lineWidth: 1,
                      },
                    });

                    start = end;
                  });

                  if (start < groupInfo.length) {
                    const startX = (start / groupInfo.length) * 100;
                    graphics.push({
                      type: "rect",
                      shape: {
                        x: startX + "%",
                        y: 420,
                        width: 100 - startX + "%",
                        height: 30,
                      },
                      style: {
                        fill:
                          typeInfo.splitIndexes.length % 2 === 0
                            ? "#e3f2fd"
                            : "#f3e5f5",
                        stroke: "#666",
                        lineWidth: 1,
                      },
                    });
                  }

                  return graphics;
                })(),
                // 添加公司分组的背景区域
                ...(() => {
                  const companyInfo = generateMergedLabelsWithSplit(
                    groupInfo,
                    "company",
                  );
                  const graphics = [];
                  let start = 0;

                  companyInfo.splitIndexes.forEach((splitIndex, i) => {
                    const end = splitIndex;
                    const startX = (start / groupInfo.length) * 100;
                    const endX = (end / groupInfo.length) * 100;

                    graphics.push({
                      type: "rect",
                      shape: {
                        x: startX + "%",
                        y: 460,
                        width: endX - startX + "%",
                        height: 30,
                      },
                      style: {
                        fill: i % 2 === 0 ? "#fff3e0" : "#e8f5e8",
                        stroke: "#333",
                        lineWidth: 2,
                      },
                    });

                    start = end;
                  });

                  if (start < groupInfo.length) {
                    const startX = (start / groupInfo.length) * 100;
                    graphics.push({
                      type: "rect",
                      shape: {
                        x: startX + "%",
                        y: 460,
                        width: 100 - startX + "%",
                        height: 30,
                      },
                      style: {
                        fill:
                          companyInfo.splitIndexes.length % 2 === 0
                            ? "#fff3e0"
                            : "#e8f5e8",
                        stroke: "#333",
                        lineWidth: 2,
                      },
                    });
                  }

                  return graphics;
                })(),
                // 添加分割线
                ...generateMergedLabelsWithSplit(
                  groupInfo,
                  "classes",
                ).splitIndexes.map((index) => ({
                  type: "line",
                  shape: {
                    x1: (index / groupInfo.length) * 100 + "%",
                    y1: 380,
                    x2: (index / groupInfo.length) * 100 + "%",
                    y2: 490,
                  },
                  style: {
                    stroke: "#999",
                    lineWidth: 1,
                  },
                })),
                ...generateMergedLabelsWithSplit(
                  groupInfo,
                  "type",
                ).splitIndexes.map((index) => ({
                  type: "line",
                  shape: {
                    x1: (index / groupInfo.length) * 100 + "%",
                    y1: 420,
                    x2: (index / groupInfo.length) * 100 + "%",
                    y2: 490,
                  },
                  style: {
                    stroke: "#d0d0d0",
                    lineWidth: 2,
                  },
                })),
                ...generateMergedLabelsWithSplit(
                  groupInfo,
                  "company",
                ).splitIndexes.map((index) => ({
                  type: "line",
                  shape: {
                    x1: (index / groupInfo.length) * 100 + "%",
                    y1: 460,
                    x2: (index / groupInfo.length) * 100 + "%",
                    y2: 490,
                  },
                  style: {
                    stroke: "#b0b0b0",
                    lineWidth: 3,
                  },
                })),
              ],
            };
          },
          updateChart() {
            const option = this.generateChartOption(this.filteredData);

            if (!this.chart) {
              this.chart = echarts.init(
                document.getElementById("multiXAxisChart"),
              );
            }

            this.chart.setOption(option, true);
          },
          objectSpanMethod({ row, column, rowIndex, columnIndex }) {
            // 只对前4列进行合并（班次、类型、单位、人员）
            if (columnIndex < 4) {
              const property = ["classes", "type", "company", "person"][
                columnIndex
              ];

              // 计算当前行在相同值组中的位置
              let spanCount = 1;
              let spanStart = rowIndex;

              // 向上查找相同值
              for (let i = rowIndex - 1; i >= 0; i--) {
                if (this.isSameGroup(this.tableData[i], row, columnIndex)) {
                  spanStart = i;
                } else {
                  break;
                }
              }

              // 向下查找相同值
              for (let i = rowIndex + 1; i < this.tableData.length; i++) {
                if (this.isSameGroup(this.tableData[i], row, columnIndex)) {
                  spanCount++;
                } else {
                  break;
                }
              }

              // 只有在组的第一行才显示合并的单元格
              if (rowIndex === spanStart) {
                return {
                  rowspan: spanCount,
                  colspan: 1,
                };
              } else {
                return {
                  rowspan: 0,
                  colspan: 0,
                };
              }
            }
          },

          isSameGroup(row1, row2, columnIndex) {
            // 根据列索引判断是否应该合并
            switch (columnIndex) {
              case 0: // 类型列
                return row1.type === row2.type;
              case 1: // 单位列
                return row1.type === row2.type && row1.company === row2.company;
              case 2: // 人员列
                return row1.type === row2.type &&
                  row1.company === row2.company &&
                       row1.person === row2.person;
              default:
                return false;
            }
          },
        },
        mounted() {
          this.initData();
          this.updateChart();

          window.addEventListener("resize", () => {
            if (this.chart) {
              this.chart.resize();
            }
          });
        },
        watch: {
          filteredData: {
            handler() {
              this.updateChart();
            },
            deep: true,
          },
        },
      });
    </script>
  </body>
</html>
